<!--
Copyright 2009, Gregg Tavares.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
    * Neither the name of Gregg Tavaresc. nor the names of his
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->

<!--
This sample shows using a render target and a shader to render a pixelated
version of the render target using colored images.
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>
O3D: Image Pixelate
</title>
<!-- Include sample javascript library functions-->
<script type="text/javascript" src="o3djs/base.js"></script>
<script type="text/javascript" id="o3dscript">
o3djs.require('o3djs.util');
o3djs.require('o3djs.math');
o3djs.require('o3djs.rendergraph');
o3djs.require('o3djs.pack');
o3djs.require('o3djs.camera');
o3djs.require('o3djs.effect');
o3djs.require('o3djs.material');
o3djs.require('o3djs.primitives');
o3djs.require('o3djs.scene');
o3djs.require('o3djs.loader');

// constants.
var RENDER_TARGET_WIDTH = 128;
var RENDER_TARGET_HEIGHT = 128;

// global variables
var g_o3dElement;
var g_o3d;
var g_math;
var g_client;
var g_pixelViewInfo;
var g_pack;
var g_teapotPack;
var g_teapotRoot;
var g_pixelRoot;
var g_textures = [];
var g_colorImageEffect;
var g_colorImageMaterial;
var g_clientWidth = 0;
var g_clientHeight = 0;
var g_tilesAcross;
var g_tilesDown;
var g_clock = 0;
var g_tiles = [];
var g_renderTargetTexture;
var g_imageTransform;
var g_noteSize;
var g_noteMesh;
var g_uvColorMultParam;
var g_uvCornerMultParam;
var g_baseColor = [0.9, 0.95, 0.7];
var g_baseColorRange = [0.1, 0.05, 0.1];

var g_onClickEvents = {
  'sc0': 'setCoverage(1, 0)',
  'sc1': 'setCoverage(0, 1)',
  'sc2': 'setCoverage(0.8, 0.2)',
  'sc3': 'setCoverageUnit()',
  'mn0': 'makeNotes(8)',
  'mn1': 'makeNotes(16)',
  'mn2': 'makeNotes(32)',
  'mn3': 'makeNotes(64)',
  'cc0': 'setColors([0.9, 0.95, 0.7], [0.1, 0.05, 0.1])',
  'cc1': 'setColors([0.5, 0.5, 0.5], [0.8, 0.8, 0.8])',
  'cc2': 'setColors([0.9, 0.9, 0.9], [0.1, 0.1, 0.1])',
  'cc3': 'setColors([1.0, 0.5, 0.5], [0.0, 0.0, 0.0])'};

/**
 * Loads a texture and stores it in g_textures indexed by filename.
 */
function loadTexture(loader, filename) {
  loader.loadTexture(g_pack,
                     o3djs.util.getAbsoluteURI('assets/' + filename),
                     rememberTexture);

  function rememberTexture(texture, exception) {
    if (exception) {
      alert(exception);
    } else {
      g_textures[filename] = texture;
    }
  }
}

/**
 * Loads a scene into the transform graph.
 * @param {!o3djs.loader.Loader} loader to use to load the scene.
 * @param {!o3d.Pack} pack Pack to load scene into.
 * @param {string} fileName filename of the scene.
 * @param {!o3d.Transform} parent parent node in the transform graph to
 *     which to load the scene into.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo who's view and projection will
 *     be set from the scene after it's loaded.
 */
function loadScene(loader, pack, fileName, parent, viewInfo) {
  // Get our full path to the scene
  var scenePath = o3djs.util.getCurrentURI() + fileName;

  // Load the file given the full path, and call the callback function
  // when its done loading.
  loader.loadScene(g_client, pack, parent, scenePath, callback);

  /**
   * Our callback is called once the scene has been loaded into memory
   * from the web or locally.
   * @param {!o3d.Pack} pack The pack that was passed in above.
   * @param {!o3d.Transform} parent The parent that was passed in above.
   * @param {*} exception null if loading succeeded.
   */
  function callback(pack, parent, exception) {
    if (exception) {
      alert('Could not load: ' + fileName + '\n' + exception);
      return;
    }
    // Get a CameraInfo (an object with a view and projection matrix)
    // using our javascript library function
    var cameraInfo = o3djs.camera.getViewAndProjectionFromCameras(
        parent,
        RENDER_TARGET_WIDTH,
        RENDER_TARGET_HEIGHT);

    // Copy the view and projection to the passed in viewInfo structure..
    viewInfo.drawContext.view = cameraInfo.view;
    viewInfo.drawContext.projection = cameraInfo.projection;

    // Generate draw elements and setup material draw lists.
    o3djs.pack.preparePack(pack, viewInfo);
  }
}

function updateProjection() {
  var newWidth = g_client.width;
  var newHeight = g_client.height;
  if (newWidth != g_clientWidth || newHeight != g_clientHeight) {
    g_clientWidth = newWidth;
    g_clientHeight = newHeight;
    g_pixelViewInfo.drawContext.projection = g_math.matrix4.orthographic(
        -g_clientWidth * 0.5 + 0.5,
        g_clientWidth * 0.5 + 0.5,
        g_clientHeight * 0.5 + 0.5,
        - g_clientHeight * 0.5 + 0.5,
        0.001,
        1000);
  }
}

/**
 * Creates the client area.
 */
function init() {
  // Comment out the line below to run the sample in the browser
  // JavaScript engine.  This may be helpful for debugging.
  o3djs.util.setMainEngine(o3djs.util.Engine.V8);

  o3djs.util.makeClients(initStep2);
}

/**
 * Initializes O3D and loads the scene into the transform graph.
 * @param {Array} clientElements Array of o3d object elements.
 */
function initStep2(clientElements) {
  // Initializes global variables and libraries.
  var o3d = clientElements[0];
  g_o3dElement = o3d;
  g_o3d = o3d.o3d;
  g_math = o3djs.math;
  g_client = o3d.client;

  // Creates a pack to manage our resources/assets
  g_pack = g_client.createPack();

  // Create a sub-pack to manage the resources required for the teapot.
  g_teapotPack = g_client.createPack();

  // Create the texture required for the color render-target.
  var texture2d = g_pack.createTexture2D(RENDER_TARGET_WIDTH,
                                         RENDER_TARGET_HEIGHT,
                                         g_o3d.Texture.XRGB8, 1, true);
  var renderSurface = texture2d.getRenderSurface(0, g_pack);
  g_renderTargetTexture = texture2d;

  // Create the depth-stencil buffer required when rendering the teapot.
  var depthSurface = g_pack.createDepthStencilSurface(RENDER_TARGET_WIDTH,
                                                      RENDER_TARGET_HEIGHT);

  g_pixelRoot = g_pack.createObject('Transform');
  g_teapotRoot = g_pack.createObject('Transform');
  g_pixelRoot.parent = g_client.root;
  g_teapotRoot.parent = g_client.root;

  var teapotRenderRoot = g_pack.createObject('RenderNode');
  teapotRenderRoot.priority = 0;
  var pixelRenderRoot = g_pack.createObject('RenderNode');
  pixelRenderRoot.priority = 1;

  teapotRenderRoot.parent = g_client.renderGraphRoot;
  pixelRenderRoot.parent = g_client.renderGraphRoot;

  var renderSurfaceSet = g_pack.createObject('RenderSurfaceSet');
  renderSurfaceSet.renderSurface = renderSurface;
  renderSurfaceSet.renderDepthStencilSurface = depthSurface;
  renderSurfaceSet.parent = teapotRenderRoot;

  // Create the render graph for a view.
  var teapotViewInfo = o3djs.rendergraph.createBasicView(
      g_teapotPack,
      g_teapotRoot,
      renderSurfaceSet);
  teapotViewInfo.clearBuffer.clearColor = [0.2, 0.3, 1, 1];
  teapotViewInfo.clearBuffer.clearColor = [1, 1, 1, 1];

  g_pixelViewInfo = o3djs.rendergraph.createBasicView(
      g_pack,
      g_pixelRoot,
      pixelRenderRoot);
  // Turn of culling so we can flip image planes if we want
  g_pixelViewInfo.zOrderedState.getStateParam('CullMode').value =
      g_o3d.State.CULL_NONE;
  // No need to write to the zbuffer.
  g_pixelViewInfo.zOrderedState.getStateParam('ZWriteEnable').value = false;
  // Set the clear color to white.
  g_pixelViewInfo.clearBuffer.clearColor = [1, 1, 1, 1];

  updateProjection();

  // Load a shader to do the pixelized look. The shader uses 2 textures.
  // One texture has the notes, the other texture determines the color of each
  // note.
  var material = o3djs.material.createMaterialFromFile(
      g_pack,
      'shaders/texture-texture-colormult-coloruv.shader',
      g_pixelViewInfo.zOrderedDrawList);
  g_colorImageMaterial = material;

  g_uvColorMultParam = material.getParam('uvColorMult');
  g_uvCornerMultParam = material.getParam('uvCornerMult');

  setCoverage(1, 0);

  // Set the texture on the material since all notes will use the
  // render target texture as their color source.
  var colorSampler = material.getParam('colorSampler').value;
  colorSampler.texture = g_renderTargetTexture;
  colorSampler.addressModeU = g_o3d.Sampler.CLAMP;
  colorSampler.addressModeV = g_o3d.Sampler.CLAMP;
  g_colorImageEffect = material.effect;

  g_pixelViewInfo.drawContext.view = g_math.matrix4.lookAt(
      [0, 0, 1],  // eye
      [0, 0, 0],  // target
      [0, 1, 0]); // up

  // Make the note mesh
  g_noteMesh = new NoteMesh(g_colorImageMaterial);

  // Make a transform to be the parent of the pixel image mesh.
  g_imageTransform = g_pack.createObject('Transform');
  g_imageTransform.addShape(g_noteMesh.shape);

  // Tell the note mesh to build a mesh of notes.
  makeNotes(16);

  // Create a transform to load the teapot on.
  var myDataRoot = g_pack.createObject('Transform');

  // Connect our root to the client.
  myDataRoot.parent = g_teapotRoot;

  // Load the scene into the transform graph as a child myDataRoot
  var loader = o3djs.loader.createLoader(initStep3);
  loadScene(loader, g_teapotPack, 'assets/teapot.o3dtgz', myDataRoot,
            teapotViewInfo);
  loadTexture(loader, 'notes.png');
  loader.finish();

  setupEvents();
}

function setupEvents() {
  // Fix up the input tags to be V8 compatible.
  var inputs = document.getElementsByTagName('input');
  for (var ii = 0; ii < inputs.length; ++ii) {
    var input = inputs[ii];
    input.onclick = g_o3dElement.eval(
        'function() {' + g_onClickEvents[input.id] + '}');
  }
}

function initStep3() {
  // Once the texture is loaded put the image in the transform graph so it
  // gets rendered.
  g_imageTransform.parent = g_pixelRoot;

  // Set the note texture on the material.
  g_colorImageMaterial.getParam('texSampler0').value.texture =
      g_textures['notes.png'];

  // Set a render callback.
  g_client.setRenderCallback(onRender);
}

/**
 * Called every frame.
 * @param {!o3d.RenderEvent} renderEvent Rendering Information.
 */
function onRender(renderEvent) {
  var elapsedTime = renderEvent.elapsedTime;
  g_clock += elapsedTime;

  updateProjection();

  // Spin the teapot.
  g_teapotRoot.identity();
  g_teapotRoot.rotateX(g_clock);
  g_teapotRoot.rotateY(g_clock * 1.3);
}

function makeNotes(tileSize) {
  g_noteSize = tileSize;
  g_noteMesh.makeNotes(tileSize);
  g_imageTransform.identity();
  g_imageTransform.translate(0, 0, -1);
  // We made each pixel 1x1 unit so scale the whole thing up to the
  // size we want.
  g_imageTransform.scale(tileSize, tileSize, 1);
}

function setColors(base, range) {
  g_baseColor = base;
  g_baseColorRange = range;
  makeNotes(g_noteSize);
}

function setCoverage(uvColorMult, uvCornerMult) {
  g_uvColorMultParam.value = [uvColorMult, uvColorMult];
  g_uvCornerMultParam.value = [uvCornerMult, uvCornerMult];
}

function setCoverageUnit() {
  g_uvColorMultParam.value = [1, 1];
  g_uvCornerMultParam.value = [1 / g_tilesAcross, 1 / g_tilesDown];
}

function random() {
  return Math.random();
  //return o3djs.Math.pseudoRandom();
}

/**
 * A NoteMesh is a class to manage the making of a mesh of notes.
 * @constructor
 */
var NoteMesh = function(material) {
  // create a shape and primitive for the vertices.
  var shape = g_pack.createObject('Shape');
  var primitive = g_pack.createObject('Primitive');
  var streamBank = g_pack.createObject('StreamBank');
  primitive.owner = shape;
  primitive.streamBank = streamBank;
  primitive.material = material;
  primitive.primitiveType = o3djs.base.o3d.Primitive.TRIANGLELIST;
  primitive.createDrawElement(g_pack, null);

  // create a buffer and fields for the data.
  var vertexBuffer = g_pack.createObject('VertexBuffer');
  this.positionField_ = vertexBuffer.createField('FloatField', 3);
  this.texcoordField_ = vertexBuffer.createField('FloatField', 2);
  this.colorField_ = vertexBuffer.createField('UByteNField', 4);
  this.colorUVField_ = vertexBuffer.createField('FloatField', 2);
  this.cornerUVField_ = vertexBuffer.createField('FloatField', 2);

  // Associate the fields with semantics for the shader
  streamBank.setVertexStream(g_o3d.Stream.POSITION, 0, this.positionField_, 0);
  streamBank.setVertexStream(g_o3d.Stream.TEXCOORD, 0, this.texcoordField_, 0);
  streamBank.setVertexStream(g_o3d.Stream.TEXCOORD, 1, this.colorUVField_, 0);
  streamBank.setVertexStream(g_o3d.Stream.TEXCOORD, 2, this.cornerUVField_, 0);
  streamBank.setVertexStream(g_o3d.Stream.COLOR, 0, this.colorField_, 0);

  // Make an index buffer.
  this.indexBuffer_ = g_pack.createObject('IndexBuffer');
  primitive.indexBuffer = this.indexBuffer_;

  this.primitive_ = primitive;
  this.shape = shape;
  this.vertexBuffer_ = vertexBuffer;
};

/**
 * Make Notes.
 * Builds a custom mesh for pixels. Each note is 1 quad and we will create
 * tileAcross * tileDown quads. Each quad uses UV coords to select 1 of the 4
 * note images. Each quad then uses a separate set of UVcoords to chose
 * a pixel from the render target for its color. Finally each quad uses
 * vertex colors to allow each note to have a slightly different tint.
 */
NoteMesh.prototype.makeNotes = function(tileSize) {
  var positions = [];
  var colors = [];
  var uvs = [];
  var colorUVs = [];
  var cornerUVs = [];
  var indices = [];

  var scaleFudge = 1.15;
  g_tilesAcross = Math.floor((g_clientWidth + tileSize - 1) / tileSize) - 1;
  g_tilesDown = Math.floor((g_clientHeight + tileSize - 1) / tileSize) - 1;
  // Limit the number of vertices so we don't need large geometry.
  // We could split the geometry but I'm lazy.
  var maxTiles = Math.floor(65534 / 6);  // 6 indices per quad.
  if (g_tilesAcross * g_tilesDown > maxTiles) {
    g_tilesAcross = Math.floor(maxTiles / g_tilesDown);
  }
  var halfX = g_tilesAcross * 0.5;
  var halfY = g_tilesDown * 0.5;
  for (var ty = 0; ty < g_tilesDown; ++ty) {
    for (var tx = 0; tx < g_tilesAcross; ++tx) {
      // pick a tint color for this quad.
      var r = g_baseColor[0] + random() * g_baseColorRange[0];
      var g = g_baseColor[1] + random() * g_baseColorRange[1];
      var b = g_baseColor[2] + random() * g_baseColorRange[2];
      var a = 1;

      // Compute the UV coords needs to look up a color in the render target.
      var cU = tx / g_tilesAcross;
      var cV = ty / g_tilesDown;

      // The base index for vertices in this quad
      var base = positions.length / 3;

      // Figure out the base position of the quad but add some randomness
      // so the notes don't line up perfectly.
      var rx = tx + random() * 4 / tileSize;
      var ry = ty + random() * 4 / tileSize;

      // Choose slight random rotation to rotate the quad so they look placed
      // by hand.
      var rot = (random() - 0.5) * o3djs.math.degToRad(10);
      var c = Math.cos(rot);
      var s = Math.sin(rot);

      // Choose the base UV coords for one of our 4 notes.
      var noteU = Math.floor(random() * 2) * 0.5;
      var noteV = Math.floor(random() * 2) * 0.5;

      // Now make a quad.
      for (var yy = 0; yy < 2; ++yy) {
        for (var xx = 0; xx < 2; ++xx) {
          // Compute 1 of our 4 points but around the origin so we can rotate
          // easily. Also scale the quad a little so the notes overlap.
          var x = (xx - 0.5) * scaleFudge;
          var y = (yy - 0.5) * scaleFudge;

          // Rotate the quad then move it back to a grid point.
          x = x * c - y * s + 0.5;
          y = y * c + x * s + 0.5;

          // Add all the pieces of the vertex.
          // The positon
          positions.push(rx + x - halfX, ry + y - halfY, 0);

          // The UVs that select a particlar note
          uvs.push(noteU + xx * 0.5, noteV + (1 - yy) * 0.5);

          // The tint color.
          colors.push(r, g, b, a);

          // The UVS that select a pixel from the render target.
          colorUVs.push(cU, cV);

          // The UVS that define corners so we can set how much of the
          // source image is displayed in each image pixel
          cornerUVs.push(xx, yy);
        }
      }
      // Push 6 indices to make 2 triangles of a quad.
      indices.push(base + 0, base + 1, base + 2, base + 2, base + 1, base + 3);
    }
  }

  // Setup the primitive for the new vertices.
  var primitive = this.primitive_;
  var numVertices = positions.length / 3;
  primitive.numberVertices = numVertices;
  primitive.numberPrimitives = indices.length / 3;

  // Allocate the vertices we need.
  this.vertexBuffer_.allocateElements(numVertices);

  // Copy all the data from JavaScript arrays into fields.
  this.positionField_.setAt(0, positions);
  this.texcoordField_.setAt(0, uvs);
  this.colorField_.setAt(0, colors);
  this.colorUVField_.setAt(0, colorUVs);
  this.cornerUVField_.setAt(0, cornerUVs);

  // Copy the indices into the index buffer.
  this.indexBuffer_.set(indices);
}

/**
 * Cleanup before exiting.
 */
function unload() {
  if (g_client) {
    g_client.cleanup();
  }
}
</script>
<style>
html, body {
  border: 0;
  margin: 0;
  height: 100%;
  height: 100%;
  text-align: center;
}
</style>
</head>
<body onload="init()" onunload="unload()">
<table style="width: 100%; height: 100%;">
<tr valign="middle">
<td align="center">
Image Pixelate: Inspired by
<a href="http://experimentalgameplay.com/blog/2009/07/play-post-i-t-shooter/">
Post IT Shooter</a>.
<div id="o3d" style="width: 100%; height: 80%;"></div>
<table width="100%"><tr>
<td align="center">
Set Coverage:<br/>
<input id="sc0" type="button" value="1pixel"/>
<input id="sc1" type="button" value="1image"/>
<input id="sc2" type="button" value="misc"/>
<input id="sc3" type="button" value="1unit"/>
</td>
<td align="center">
Set Note Size:<br/>
<input id="mn0" type="button" value="8"/>
<input id="mn1" type="button" value="16"/>
<input id="mn2" type="button" value="32"/>
<input id="mn3" type="button" value="64"/>
</td>
<td align="center">
Set Colors:<br/>
<input id="cc0" type="button" value="greenish"/>
<input id="cc1" type="button" value="random"/>
<input id="cc2" type="button" value="light"/>
<input id="cc3" type="button" value="red"/>
</td>
</tr></table>
</td>
</tr>
</table>
</body>
</html>
